﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;

namespace Pg.BioMedics.SDR.Services.Thesaurus
{
    /// <summary>
    /// Provides english language thesaurus functionality
    /// </summary>
    internal sealed class Thesaurus : IDisposable
    {
        #region Constants

        private const string IndexResource = "Pg.BioMedics.SDR.Services.Resources.Thesaurus.th_en_US_new.idx";
        private const string DataResource = "Pg.BioMedics.SDR.Services.Resources.Thesaurus.th_en_US_new.dat";
        private const string StoppingsResource = "Pg.BioMedics.SDR.Services.Resources.SMART.english.stop";

        private const char Separator = '|';

        #endregion

        #region Fields

        private static Thesaurus instance;             

        private Dictionary<string, long> index;
        private MemoryStream data;
        private StringCollection stoppings;

        private string encoding;
        private long count;

        #endregion

        #region Constructors

        /// <summary>
        /// Creates Thesaurus and provides some initializations
        /// </summary>
        private Thesaurus()
        {
            Initialize();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets Thesaurus instance
        /// </summary>
        internal static Thesaurus Instance
        {
            get
            {
                if (instance == null)
                    instance = new Thesaurus();
                else if (instance.data == null)
                    instance = new Thesaurus();

                return instance;
            }
        }

        /// <summary>
        /// Gets Thesaurus strings encoding
        /// </summary>
        internal string Encoding
        {
            get
            {
                return this.encoding;
            }
        }

        /// <summary>
        /// Get number of entries in Thesaurus
        /// </summary>
        internal long Count
        {
            get
            {
                return this.count;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets list of meanings assigned to the 
        /// </summary>
        /// <param name="word"></param>
        /// <returns></returns>
        internal Meaning[] GetMeanings(string word)
        {
            // input params validation
            if (String.IsNullOrEmpty(word))
                throw new ArgumentException("Word cannot be null or empty string", "word");
            else if (!this.index.ContainsKey(word.ToLower()))
                return new Meaning[0];

            // processing           
            long offset = this.index[word.ToLower()];

            this.data.Seek(offset, SeekOrigin.Begin);
            StreamReader reader = new StreamReader(this.data);
            string definition = reader.ReadLine();

            string[] components = definition.Split(Separator);
            int count = Int32.Parse(components[1]);

            Meaning[] meanings = new Meaning[count];
            for (int i = 0; i < count; i++)
                meanings[i] = new Meaning(reader.ReadLine());

            return meanings;
        }

        /// <summary>
        /// Checks if given word is a stopping word according to Cornell University SMART 
        /// definition
        /// </summary>
        /// <param name="word">Single word</param>
        /// <returns>true - if word is a stopping word</returns>
        internal bool IsStoppingWord(string word)
        {
            // input params validation
            if (String.IsNullOrEmpty(word))
                return true;

            // processing
            return this.stoppings.Contains(word.ToLower());
        }

        /// <summary>
        /// Supporting method extracting words from the text
        /// </summary>
        /// <param name="text">text</param>
        /// <returns>List of words</returns>
        internal string[] GetWords(string text)
        {
            // input params validation
            if (String.IsNullOrEmpty(text))
                throw new ArgumentException("Text cannot be null or empty string", "text");

            // processing    
            IList<string> words = new List<string>();
            foreach (string extractedWord in (from word in text.Split(' ')
                                              select word.ToLower().Trim()))
            {
                if (!words.Contains(extractedWord))
                    words.Add(extractedWord);
            }

            return words.ToArray();

        }

        #endregion        

        #region Support methods

        /// <summary>
        /// Provides Thesaurus initialization logic - index and content 
        /// is loaded
        /// </summary>
        private void Initialize()
        {
            // load indexes
            using (Stream indexStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(IndexResource))
            {
                using (StreamReader reader = new StreamReader(indexStream))
                {
                    this.index = new Dictionary<string, long>();

                    // get encoding 
                    this.encoding = reader.ReadLine();
                    this.count = Int64.Parse(reader.ReadLine());

                    while (!reader.EndOfStream)
                    {
                        string line = reader.ReadLine();
                        string[] components = line.Split(Separator);
                        if (components.Length == 2)
                        {
                            long offset = 0;
                            if (Int64.TryParse(components.Last(), out offset))
                                this.index.Add(components.First(), offset);
                            else
                                this.index.Add(components.First(), offset);
                        }
                        else
                            this.index.Add(components.First(), 0);
                    }
                }
            }

            // fill data buffer
            using (Stream dataStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(DataResource))
            {
                this.data = new MemoryStream();

                byte[] buffer = new byte[dataStream.Length];
                while ((dataStream.Read(buffer, 0, buffer.Length) > 0))
                    this.data.Write(buffer, 0, buffer.Length);
            }

            // load stoppings
            using (Stream stoppingStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(StoppingsResource))
            {
                using (StreamReader reader = new StreamReader(stoppingStream))
                {
                    this.stoppings = new StringCollection();
                    while (!reader.EndOfStream)
                        this.stoppings.Add(reader.ReadLine());
                }
            }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Releases all unmanaged resources
        /// </summary>
        public void Dispose()
        {
            if (this.data != null)
            {
                this.data.Dispose();
                this.data = null;
            }
        }

        #endregion
    }
}
